/*
 * SIT_Widget.c : base class for widget creation. Dispatch tags to custom
 * class and handle messages.
 *
 * Written by T.Pierron, Feb 2006.
 */

#include <stdarg.h>
#include "SIT_P.h"
#include "graphics_P.h"

	/* Common properties for all widgets */
	TagList WidgetClass[] = {
		{ SIT_Title,        "title",        CSG, SIT_STR,  OFFSET(SIT_Widget, sw_Title) },
		{ SIT_UserData,     "userData",     _SG, SIT_PTR,  OFFSET(SIT_Widget, sw_UserData) },
		{ SIT_ResizePolicy, "resizePolicy", _SG, SIT_INT,  OFFSET(SIT_Widget, sw_ResizePolicy) },
		{ SIT_Foreground,   "foreground",   _SG, SIT_LONG, OFFSET(SIT_Widget, sw_Fg) },
		{ SIT_Background,   "background",   _SG, SIT_LONG, OFFSET(SIT_Widget, sw_Bg) },
		{ SIT_Enabled,      "enabled",      _SG, SIT_BOOL, OFFSET(SIT_Widget, sw_Enabled) },
		{ SIT_Visible,      "visible",      CSG, SIT_BOOL, OFFSET(SIT_Widget, sw_Visible) },
		{ SIT_TabOrder,     "tabOrder",     _SG, SIT_INT,  OFFSET(SIT_Widget, sw_TabOrder) },
		{ SIT_Height,       "height",       CSG, SIT_INT,  OFFSET(SIT_Widget, sw_Fixed.height) },
		{ SIT_Width,        "width",        CSG, SIT_INT,  OFFSET(SIT_Widget, sw_Fixed.width) },
		{ SIT_Y,            "y",            CSG, SIT_INT,  OFFSET(SIT_Widget, sw_Fixed.y) },
		{ SIT_X,            "x",            CSG, SIT_INT,  OFFSET(SIT_Widget, sw_Fixed.x) },
		{ SIT_Rect,         "rect",         CSG, SIT_ABBR, ABBR(1, 1, 1, 1) },
		{ SIT_Font,         "font",         CSG, SIT_PTR,  OFFSET(SIT_Widget, sw_Font) },
		{ SIT_Cursor,       "cursor",       _SG, SIT_INT,  OFFSET(SIT_Widget, sw_Cursor) },
		{ SIT_CursorImage,  "cursorImage",  _SG, SIT_PTR,  OFFSET(SIT_Widget, sw_Cursor) },
		{ SIT_Handle,       "handle",       __G, SIT_PTR,  OFFSET(SIT_Widget, sw_Handle) },
		{ SIT_Parent,       "parent",       __G, SIT_PTR,  OFFSET(SIT_Widget, sw_Parent) },
		{ SIT_CtrlType,     "ctrlType",     __G, SIT_INT,  OFFSET(SIT_Widget, sw_Type) },
		{ SIT_BuddyCtrl,    NULL,           C__, SIT_PTR,  0},
		{ SIT_BuddyText,    NULL,           C__, SIT_STR,  OFFSET(SIT_Widget, sw_BuddyText) },
		{ SIT_BuddyLabel,   "buddyLabel",   C__, SIT_ABBR, ABBR(1, 1, 0, 0)},
		{ SIT_ToolTip,      "toolTip",      C__, SIT_STR,  OFFSET(SIT_Widget, sw_ToolTip) },

		/* Attachments */
		#define	ATTACH(side, field)    OFFSET(SIT_Widget, sw_Attachment[side].field)
		{ SIT_LeftOffset,       "leftOffset",       CSG, SIT_INT,  ATTACH(0, sa_Offset) },
		{ SIT_TopOffset,        "topOffset",        CSG, SIT_INT,  ATTACH(1, sa_Offset) },
		{ SIT_RightOffset,      "rightOffset",      CSG, SIT_INT,  ATTACH(2, sa_Offset) },
		{ SIT_BottomOffset,     "bottomOffset",     CSG, SIT_INT,  ATTACH(3, sa_Offset) },
		{ SIT_LeftObject,       "leftObject",       CSG, SIT_CTRL, ATTACH(0, sa_Arg) },
		{ SIT_TopObject,        "topObject",        CSG, SIT_CTRL, ATTACH(1, sa_Arg) },
		{ SIT_RightObject,      "rightObject",      CSG, SIT_CTRL, ATTACH(2, sa_Arg) },
		{ SIT_BottomObject,     "bottomObject",     CSG, SIT_CTRL, ATTACH(3, sa_Arg) },
		{ SIT_LeftAttachment,   "leftAttachment",   CSG, SIT_INT,  ATTACH(0, sa_Type) },
		{ SIT_TopAttachment,    "topAttachment",    CSG, SIT_INT,  ATTACH(1, sa_Type) },
		{ SIT_RightAttachment,  "rightAttachment",  CSG, SIT_INT,  ATTACH(2, sa_Type) },
		{ SIT_BottomAttachment, "bottomAttachment", CSG, SIT_INT,  ATTACH(3, sa_Type) },
		{ SIT_Left,             "left",             CSG, SIT_ABBR, ABBR(4, 4, 4, 0) },
		{ SIT_Top,              "top",              CSG, SIT_ABBR, ABBR(4, 4, 4, 0) },
		{ SIT_Right,            "right",            CSG, SIT_ABBR, ABBR(4, 4, 4, 0) },
		{ SIT_Bottom,           "bottom",           CSG, SIT_ABBR, ABBR(4, 4, 4, 0) },
		{ SIT_MarginLeft,       "marginLeft",       CSG, SIT_INT,  OFFSET(SIT_Widget, sw_Margins[0]) },
		{ SIT_MarginBottom,     "marginBottom",     CSG, SIT_INT,  OFFSET(SIT_Widget, sw_Margins[3]) },
		{ SIT_MarginRight,      "marginRight",      CSG, SIT_INT,  OFFSET(SIT_Widget, sw_Margins[2]) },
		{ SIT_MarginTop,        "marginTop",        CSG, SIT_INT,  OFFSET(SIT_Widget, sw_Margins[1]) },
		{ SIT_Margins,          "margins",          CSG, SIT_ABBR, ABBR(1, 1, 1, 1) },
		{ SIT_MaxWidth,         "maxWidth",         CSG, SIT_CTRL, 0 },
		{ SIT_MinWidth,         "minWidth",         CSG, SIT_LONG, OFFSET(SIT_Widget, sw_MinBox.cx) },
		{ SIT_MinHeight,        "minHeight",        CSG, SIT_LONG, OFFSET(SIT_Widget, sw_MinBox.cy) },
		#undef ATTACH

		{ SIT_TagEnd }
	};

	static int sizeof_widgets[] = {
		sizeof (struct SIT_App_t),
		sizeof (struct SIT_Dialog_t),
		sizeof (struct SIT_Label_t),
		sizeof (struct SIT_PushButton_t),
		sizeof (struct SIT_EditBox_t),
		sizeof (struct SIT_Widget_t), // Frame
		sizeof (struct SIT_ListBox_t),
		sizeof (struct SIT_Canvas_t),
		sizeof (struct SIT_ScrollBar_t),
		sizeof (struct SIT_Slider_t),
		sizeof (struct SIT_Progress_t),
		sizeof (struct SIT_ComboBox_t),
		sizeof (struct SIT_DateTime_t),
		sizeof (struct SIT_Tab_t),
		sizeof (struct SIT_Splitter_t),
		sizeof (struct SIT_ToolBar_t),
		sizeof (struct SIT_Tooltip_t),
		sizeof (struct SIT_Status_t),
		sizeof (struct SIT_FileDialog_t),
		sizeof (struct SIT_ColorDialog_t),
		sizeof (struct SIT_FontDialog_t),
		sizeof (struct SIT_FolderSel_t)
	};

	static int geomPolicy[] = {
		FitUsingCurrentBox,  // SITV_Auto
		KeepDialogSize,      // SITV_Fixed
		FitUsingOptimalBox   // SITV_Optimal
	};

	extern LPWSTR win32_predef[];

void SIT_SetTitle(SIT_Widget w)
{
	if (IsDef(w->sw_Title))
	{
		LPWSTR buf;

		UTF8ToUTF16(w->sw_Title, buf);

		SetWindowText(w->sw_Handle, buf);
	}
	else SetWindowText(w->sw_Handle, L"");
}

static void SIT_ChangePos(SIT_Widget after, SIT_Widget ins)
{
	ListHead * head = &ins->sw_Parent->sw_Children;

	ListRemove(head, &ins->sw_Node);
	if (after) ListInsert(head, &ins->sw_Node, &after->sw_Node);
	else       ListAddHead(head, &ins->sw_Node);

	SetWindowPos(ins->sw_Handle, after ? after->sw_Handle : HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}

/* Reorder z-index of control. Widgets are sorted using sw_TabOrder key. */
static void SIT_ReorderWidget(SIT_Widget w)
{
	SIT_Widget c;
	ULONG style = GetWindowLong(w->sw_Handle, GWL_STYLE);
	int idx = w->sw_TabOrder & 0xffff;

	if (idx > 0)
	{
		if ((style & WS_TABSTOP) == 0)
			SetWindowLong(w->sw_Handle, GWL_STYLE, style | WS_TABSTOP);

		for (c = HEAD(w->sw_Parent->sw_Children); c && (c == w || (c->sw_TabOrder & 0xffff) <= idx); NEXT(c));

		if (c && c->sw_Node.ln_Next != &w->sw_Node)
		{
			// Reorder
			SIT_ChangePos(c, w);
		}
	}
	else if (style & WS_TABSTOP) /* Remove TABSTOP style */
	{
		SetWindowLong(w->sw_Handle, GWL_STYLE, style & ~WS_TABSTOP);
	}
}

static void SIT_EnableSub(SIT_Widget w, Bool ena)
{
	SIT_Widget child;

	EnableWindow(w->sw_Handle, ena);

	for (child = HEAD(w->sw_Children); child; NEXT(child))
	{
		if (! w->sw_SuperProc && child->sw_Type != SIT_TAB)
			SIT_EnableSub(child, child->sw_Enabled && ena);
	}
}

/* Manage SIT_Visible property */
static void SIT_SetVisible(SIT_Widget w, Bool show)
{
	Bool visible = IsWindowVisible(w->sw_Handle) > 0;
	SIT_Widget c;

	if (visible != show)
	{
		w->sw_Flags |= SITF_GeometryChanged;
		/*
		 * Set window size to 0x0: hack to avoid clipping issues. A pending WM_GEOMETRY
		 * event has already been posted and will resize controls anyway.
		 */
		SetWindowPos(w->sw_Handle, NULL, 0, 0, 0, 0, SWP_NOREPOSITION | SWP_NOZORDER);
		ShowWindow(w->sw_Handle, (w->sw_Visible = show) ? SW_SHOW : SW_HIDE);

		if (! show) w->sw_CurrentBox.cx = w->sw_Box.width  - w->sw_Box.x,
		            w->sw_CurrentBox.cy = w->sw_Box.height - w->sw_Box.y;
		else w->sw_Flags |= SITF_RestoreSize;

		if (w->sw_Type == SIT_FRAME)
		{
			/* Children of frame are just put above the this control to give the illusion
			 * that they are children. Buf if the frame is hidden, SIT children won't.
			 */
			for (c = HEAD(w->sw_Children); c; NEXT(c))
			{
				if (! c->sw_Visible) continue;
				SetWindowPos(c->sw_Handle, NULL, 0, 0, 0, 0, SWP_NOREPOSITION | SWP_NOZORDER);
				ShowWindow(c->sw_Handle, show ? SW_SHOW : SW_HIDE);
				if (c->sw_Type != SIT_FRAME)
				{
					while (c->sw_Node.ln_Next == NULL && c->sw_Parent != w)
						c = c->sw_Parent;
					NEXT(c);
				}
				else c = HEAD(c->sw_Children);
			}
		}
	}
	else ShowWindow(w->sw_Handle, (w->sw_Visible = show) ? SW_SHOW : SW_HIDE);
}

int SIT_SetWidgetValue(SIT_Widget w, APTR cd, APTR ud)
{
	TagList *     tag = cd;
	SIT_Variant * val = ud;

	if (w->sw_Visible && tag->tl_Flags == CSG &&
	    /* Resize policy set to fixed means that control cannot grow by setting the title */
	    ! (tag->tl_TagID == SIT_Title && (w->sw_ResizePolicy == SITV_Fixed || w->sw_Type == SIT_DIALOG || w->sw_Type == SIT_EDITBOX)))
	{
		APTR p = (STRPTR)w + tag->tl_Arg;
		switch (tag->tl_Type) {
		case SIT_INT:  if (* (int  *) p == val->integer) return 0; break;
		case SIT_LONG: if (* (long *) p == val->longInt) return 0; break;
		case SIT_BOOL: if (* (Bool *) p == val->boolean) return 0;
		/* No need to check SIT_CTRL, SIT_PTR, SIT_DBL and SIT_STR */
		}
		w->sw_Flags |= SITF_GeometryChanged;
	}

	if (tag->tl_TagID == SIT_CursorImage || tag->tl_TagID == SIT_Cursor)
	{
		if (w->sw_Flags & SITF_CustomCursor)
			DestroyCursor(w->sw_Cursor), w->sw_Flags &= ~SITF_CustomCursor;
		if (val->integer < 0x20)
			w->sw_Cursor = LoadCursor(NULL, win32_predef[val->integer]);
		else
			w->sw_Cursor = SIT_ImageToCursor(val->pointer), w->sw_Flags |= SITF_CustomCursor;
		if (w->sw_Handle)
		{
			POINT pt;
			GetCursorPos(&pt);
			ScreenToClient(w->sw_Handle, &pt);
			if (ChildWindowFromPoint(w->sw_Handle, pt) == w->sw_Handle)
				SetCursor(w->sw_Cursor);
		}
		return 0;
	}

	if (tag->tl_Arg > 0)
	{
		STRPTR * str;
		#define	SET_VALUE(widget, type, val) \
			* (type *) ((char *)widget + tag->tl_Arg) = val

		switch (tag->tl_Type) {
		case SIT_CTRL:
		case SIT_PTR:  SET_VALUE(w, void *, val->pointer); break;
		case SIT_INT:  SET_VALUE(w, int,    val->integer); break;
		case SIT_LONG: SET_VALUE(w, long,   val->longInt); break;
		case SIT_DBL:  SET_VALUE(w, double, val->real);    break;
		case SIT_BOOL: SET_VALUE(w, Bool,   val->boolean); break;
		case SIT_STR:
			str = (STRPTR *) ((STRPTR)w + tag->tl_Arg);
			if (*str) free(*str);
			*str = val->string;
		}
	}

	/* Special processing for a few tags */
	switch (tag->tl_TagID) {
	case SIT_Title:
		// if (w->sw_Type == SIT_EDITBOX && HAS_1OF2EVT(w, SITE_OnChange, SITE_OnChanged))
		//	((SIT_EditBox)w)->se_CancelEvent = 1; /* Prevent OnChanged cb */
		SIT_SetTitle(w);
		break;
	case SIT_Visible:    SIT_SetVisible(w, val->integer); break;
	case SIT_Enabled:    SIT_EnableSub(w, w->sw_Enabled = val->integer); break;
	case SIT_X:          w->sw_Flags |= SITF_FixedX; break;
	case SIT_Y:          w->sw_Flags |= SITF_FixedY; break;
	case SIT_Foreground: w->sw_Flags |= SITF_HasCustFg; break;
	case SIT_Background: w->sw_Flags |= SITF_HasCustBg; break;
	case SIT_Font:
	{	HFONT old = w->sw_Font;
		w->sw_Font = GFX_GetFont((STRPTR)w->sw_Font);
		if (w->sw_Handle) SendMessage(w->sw_Handle, WM_SETFONT, (WPARAM) w->sw_Font, FALSE);
		if (old && old != sithfont) DeleteObject(old);
		if (w->sw_Handle)
		{
			if (HAS_EVT(w, SITE_OnResize))
			{
				/* The control will probably have to recompute some stuff */
				RECT rect;
				GetClientRect(w->sw_Handle, &rect);
				SIT_ApplyCallback(w, &rect.right, SITE_OnResize);
			}
			if (HAS_EVT(w, SITE_OnPaint))
			{
				/* ... and redraw some stuff too */
				LONG style = GetClassLong(w->sw_Handle, GCL_STYLE);
				InvalidateRect(w->sw_Handle, NULL, (style & (CS_HREDRAW|CS_VREDRAW)) > 0);
			}
		}
	}	break;
	case SIT_TabOrder:
		if (w->sw_Handle) SIT_ReorderWidget(w);
		break;
	case SIT_ToolTip:
		if (w->sw_ToolTipH) SIT_DestroyWidget(w->sw_ToolTipH), w->sw_ToolTipH = NULL;
		if (val->string)
			w->sw_ToolTipH = SIT_CreateTip(w, val->string), w->sw_ToolTip = NULL;
		break;
	case SIT_Width:
		if (w->sw_Type == SIT_DIALOG) w->sw_Box.width = w->sw_Box.x + w->sw_Fixed.width, w->sw_Flags |= SITF_KeepDiagSize; // Used by KeepDialogSize
		w->sw_Flags |= SITF_FixedWidth;
		break;
	case SIT_Height:
		if (w->sw_Type == SIT_DIALOG) w->sw_Box.height = w->sw_Box.y + w->sw_Fixed.height, w->sw_Flags |= SITF_KeepDiagSize;
		w->sw_Flags |= SITF_FixedHeight;
		break;
	case SIT_BuddyCtrl:
		if (w->sw_BuddyText)
			SIT_CreateBuddyLabel(w, w->sw_BuddyText, ((SIT_Variant *)ud)->pointer);
		break;
	case SIT_MaxWidth:
		if (val->pointer)
		{
			/* Add to linked list */
			SIT_Widget prev = val->pointer;
			SIT_Widget next = (APTR) prev->sw_Max.ln_Next;
			prev->sw_Max.ln_Next = (APTR) w;
			w->sw_Max.ln_Prev = (APTR) prev;
			w->sw_Max.ln_Next = (APTR) next;
			if (next) next->sw_Max.ln_Prev = (APTR) w;
		}
		else /* Remove from linked list */
		{
			SIT_Widget prev = (APTR) w->sw_Max.ln_Prev;
			SIT_Widget next = (APTR) w->sw_Max.ln_Next;

			if (prev) prev->sw_Max.ln_Next = (APTR) next;
			if (next) next->sw_Max.ln_Prev = (APTR) prev;
		}
	}
	return 0;
}

static int SIT_SortControls(ListNode * n1, ListNode * n2)
{
	return (((SIT_Widget)n1)->sw_TabOrder & 0xffff) - (((SIT_Widget)n2)->sw_TabOrder & 0xffff);
}

DLLIMP SIT_Widget SIT_CreateWidget(STRPTR name, int type, SIT_Widget parent, ...)
{
	static char notabstop[] = {SIT_DIALOG, SIT_SCROLLBAR, SIT_SPLITTER, 0};

	if (name == NULL) return NULL; /* Mandatory */

	/* Status bar can only be added to dialog */
	if (parent == NULL && type != SIT_APP) return NULL;
	if (type == SIT_STATUSBAR && parent->sw_Type != SIT_DIALOG) return NULL;
	if (type == SIT_APP && app) return &app->super;

	int        len = utf16len(name);
	SIT_Widget w   = calloc(sizeof_widgets[type] + len * sizeof (WCHAR), 1);
	SIT_Widget d   = parent;
	va_list    args;
	Bool       ok;

	while (d && d->sw_Type != SIT_DIALOG) d = d->sw_Parent;

	w->sw_Name     = (APTR) w + sizeof_widgets[type];
	w->sw_Type     = type;
	w->sw_Parent   = parent;
	w->sw_Class    = WidgetClass;
	w->sw_SetValue = SIT_SetWidgetValue;
	w->sw_Enabled  = True;
	w->sw_Visible  = type != SIT_DIALOG;
	w->sw_MinBox.cx = w->sw_MinBox.cy = -1;

	if (parent && strchr(notabstop, type) == NULL)
	{
		/* Pre-fill tab: it is up to the creation callback to use or not this value */
		w->sw_TabOrder = d->sw_TabOrder += 5;
	}

	MultiByteToWideChar(CP_UTF8, 0, name, -1, w->sw_Name, len);

	va_start(args, parent);

	switch (type) {
	case SIT_APP:         ok = SIT_CreateApp(w, args);          break;
	case SIT_DIALOG:      ok = SIT_CreateDialog(w, args);       break;
	case SIT_LABEL:       ok = SIT_CreateLabel(w, args);        break;
	case SIT_PUSHBUTTON:  ok = SIT_CreateButton(w, args);       break;
	case SIT_EDITBOX:     ok = SIT_CreateEditBox(w, args);      break;
	case SIT_FRAME:       ok = SIT_CreateFrame(w, args);        break;
	case SIT_LISTBOX:     ok = SIT_CreateListBox(w, args);      break;
	case SIT_CANVAS:      ok = SIT_CreateCanvas(w, args);       break;
	case SIT_SCROLLBAR:   ok = SIT_CreateScrollBar(w, args);    break;
	case SIT_SLIDER:      ok = SIT_CreateSlider(w, args);       break;
	case SIT_PROGRESS:    ok = SIT_CreateProgress(w, args);     break;
	case SIT_COMBOBOX:    ok = SIT_CreateComboBox(w, args);     break;
	case SIT_DATETIME:    ok = SIT_CreateDateTime(w, args);     break;
	case SIT_TAB:         ok = SIT_CreateTab(w, args);          break;
	case SIT_SPLITTER:    ok = SIT_CreateSplitter(w, args);     break;
	case SIT_TOOLBAR:     ok = SIT_CreateToolbar(w, args);      break;
	case SIT_TOOLTIP:     ok = SIT_CreateTooltip(w, args);      break;
	case SIT_STATUSBAR:   ok = SIT_CreateStatus(w, args);       break;
	case SIT_FILESELECT:  ok = SIT_CreateFileDialog(w, args);   break;
	case SIT_COLORSELECT: ok = SIT_CreateColorDialog(w, args);  break;
	case SIT_FONTSELECT:  ok = SIT_CreateFontDialog(w, args);   break;
	case SIT_DIRSELECT:   ok = SIT_CreateFolderDialog(w, args); break;
	default:              ok = False;
	}
	va_end(args);

	if (ok == False && w) free(w), w = NULL;
	if (w == NULL) return NULL;
	w->sw_OptimalBox = w->sw_MinBox;

	if (parent)
	{
		/* Only one status bar can be assoaciated to one top-level window */
		if (type == SIT_STATUSBAR)
		{
			SIT_Dialog d = (SIT_Dialog) parent;

			if (d->sd_Status)
				DestroyWindow(d->sd_Status->sw_Handle);

			d->sd_Status = w;
		}
		else
		{
			ListInsertSort(&parent->sw_Children, &w->sw_Node, SIT_SortControls);
			if (w->sw_Node.ln_Next)
				SIT_ChangePos((APTR) w->sw_Node.ln_Prev, w);
		}

		/* If we just added children to this container, setup WS_EX_CONTROLPARENT flag */
		if (HAS_TAB(w) && parent->sw_Type != SIT_FRAME)
		{
			SetWindowLong(parent->sw_Handle, GWL_EXSTYLE, GetWindowLong(parent->sw_Handle, GWL_EXSTYLE) | WS_EX_CONTROLPARENT);
		}

		if (IsWindowVisible(parent->sw_Handle) && w->sw_Visible && !(w->sw_Flags & SITF_TopLevel) &&
		    (d->sw_Flags & SITF_GeomNotified) == 0)
		{
			d->sw_Flags |= SITF_GeomNotified;
			PostMessageW(d->sw_Handle, WM_GEOMETRY, 0, geomPolicy[d->sw_ResizePolicy]);
		}
	}
	/* Handle didn't exist at creation time */
	if (w->sw_Enabled == False)
		EnableWindow(w->sw_Handle, FALSE);

	return w;
}

void SIT_ParseTags(SIT_Widget w, va_list vargs, TagList * classArgs)
{
	KeyVal list = NULL;
	int    tag;

	next: for (;;)
	{
		/* Look in classArgs for definition of that tag */
		TagList *   stack[5];
		TagList *   args;
		SIT_Variant value;
		ULONG       usage = 0;
		BOOL        format;

		if (list) tag = list->tag;
		else      tag = va_arg(vargs, int);
		format = tag & XfMt ? TRUE : FALSE;
		tag &= ~ XfMt;

		if (tag == SIT_TagEnd) break;
		if (tag == SIT_TagList) { list = list ? (KeyVal)list->key.ptr : va_arg(vargs, KeyVal); continue; }
		if (tag >= SIT_TagUser)
		{
			SIT_OnVal cd = {.stage = SITV_Set, .tag = tag};
			if (list) cd.ptr = (APTR) &list->key;
			else      cd.vararg = &vargs;
			tag = SIT_ApplyCallback(w, &cd, SITE_OnSetOrGet);
			if (tag & 1) w->sw_Flags |= SITF_GeometryChanged;
			if (tag & 2) w->sw_Flags |= SITF_PostProcess;
			continue;
		}

		for (args = classArgs; args->tl_TagID != SIT_TagEnd; )
		{
			/* Super class */
			if (args->tl_TagID == SIT_SuperClass)
			{
				stack[usage ++] = args + 1;
				args = (TagList *) args->tl_Arg;
				continue;
			}
			if (args->tl_TagID != tag)
			{
				args ++;
				if (args->tl_TagID == 0 && usage > 0)
					args = stack[-- usage];
				continue;
			}
			if (args->tl_Type == SIT_ABBR)
				tag = args->tl_Arg, args -= tag & 0xff, tag >>= 8;
			else
				tag = 0;

			for (;;)
			{
				if (list) {
					switch (args->tl_Type) {
					case SIT_CTRL: case SIT_PTR: value.pointer = list->key.ptr; break;
					case SIT_LONG: case SIT_BOOL: case SIT_INT: value.integer = list->key.val; break;
					case SIT_STR:  value.string = strdup(list->key.ptr);
					}
					list ++;
					if (list->tag == SIT_TagList)
						list = list->key.ptr;
				}
				else switch (args->tl_Type) {
				case SIT_CTRL:
				case SIT_PTR:  value.pointer = va_arg(vargs, void *); break;
				case SIT_BOOL:
				case SIT_INT:  value.integer = va_arg(vargs, int);    break;
				case SIT_LONG: value.longInt = va_arg(vargs, long);   break;
				case SIT_DBL:  value.real    = va_arg(vargs, double); break;
				case SIT_STR: /* Strings have to be malloc()'ed */
					value.string = va_arg(vargs, STRPTR);
					if (value.string)
						value.string = format ? RawDoFmt(value.string, &vargs) : strdup(value.string);
				}
				w->sw_SetValue(w, args, &value);
				if (tag == 0) break;
				args -= tag & 0xff; tag >>= 8;
			}
			goto next;
		}
		/* Not good: tag not in definition list */
		va_arg(vargs, int);
	}
}

DLLIMP Bool SIT_AddCallback(SIT_Widget w, int type, SIT_CallProc proc, APTR data)
{
	SIT_Callback cb;
	int evt = type & ~SITE_AddOrUpdate;

	if (w == NULL) return False;
	if (evt == SITE_OnClipChanged && GetWindowLong(w->sw_Handle, GWL_WNDPROC) != (LONG) SIT_SubClassHandler)
		return False;

	if (evt == SITE_OnClickMove)
	{
		/* Register 2 events at once */
		evt = type & SITE_AddOrUpdate;
		return SIT_AddCallback(w, evt | SITE_OnClick,     proc, data) &&
		       SIT_AddCallback(w, evt | SITE_OnMouseMove, proc, data);
	}

	if (type & SITE_AddOrUpdate)
	{
		/* Want to update user data associated with callback */
		for (cb = HEAD(w->sw_Callbacks); cb && ! (cb->sc_CB == proc && cb->sc_Event == evt); NEXT(cb));
		if (cb) { cb->sc_UserData = data; return True; }
	}
	/* Check that callback didn't exist yet */
	else for (cb = HEAD(w->sw_Callbacks); cb && ! (cb->sc_CB == proc &&
	          cb->sc_UserData == data && cb->sc_Event == evt); NEXT(cb));

	if (cb == NULL && proc)
	{
		cb = malloc(sizeof *cb);

		if (cb)
		{
			cb->sc_CB       = proc;
			cb->sc_UserData = data;
			cb->sc_Event    = evt;
			SET_EVT(w, evt);

			/* Last In, First Apply order */
			ListAddHead(&w->sw_Callbacks, &cb->sc_Node);

			switch (evt) {
			case SITE_OnPaint:
				if (w->sw_Type == SIT_COMBOBOX)
				{
					BOOL SIT_RecreateComboBox(SIT_Widget w, int style);
					SIT_RecreateComboBox(w, GetWindowLong(w->sw_Handle, GWL_STYLE) | CBS_OWNERDRAWFIXED);
				}
				break;
			case SITE_OnClipChanged:
				if (! HAS_EVT(w, SITE_OnClipChanged))
				{
					/* Must not be done twice */
					w->sw_Chain = SetClipboardViewer(w->sw_Handle);
				}
				break;
			case SITE_OnDropFiles:
				DragAcceptFiles(w->sw_Handle, True);
			}
			return True;
		}
	}
	return False;
}

DLLIMP void SIT_DelCallback(SIT_Widget w, int type, SIT_CallProc proc, APTR data)
{
	SIT_Callback cb;

	for (cb = HEAD(w->sw_Callbacks); cb && cb->sc_CB != proc &&
	     cb->sc_UserData != data && cb->sc_Event != type; NEXT(cb));

	if (cb)
	{
		switch (type) {
		case SITE_OnTimer:     KillTimer(w->sw_Handle, (UINT) cb->sc_UserData); break;
		case SITE_OnDropFiles: DragAcceptFiles(w->sw_Handle, False);
		}
		ListRemove(&w->sw_Callbacks, &cb->sc_Node);
		free(cb);
		for (w->sw_EvtFlags = 0, cb = HEAD(w->sw_Callbacks); cb; NEXT(cb))
			SET_EVT(w, cb->sc_Event);
	}
}

/* Take some things into account that AdjustWindowEx can't */
void SIT_AdjustWindowRect(SIT_Dialog d, RECT * rect, RECT * adj_lt)
{
	int height = 0;

	/* Status bar is a special control */
	if (d->sd_Status && d->sd_Status->sw_Visible)
	{
		GetWindowRect(d->sd_Status->sw_Handle, rect);
		height = rect->bottom - rect->top;
	}

	rect->left   = (adj_lt && (d->super.sw_Flags & SITF_FixedX) ? d->super.sw_Fixed.x : 0);
	rect->top    = (adj_lt && (d->super.sw_Flags & SITF_FixedY) ? d->super.sw_Fixed.y : 0);
	rect->right  = rect->left + d->super.sw_Box.width;
	rect->bottom = rect->top  + d->super.sw_Box.height;

	/* XXX AdjustWindowRectEx does not take into account multi-line menu bar */
	AdjustWindowRectEx(rect, d->sd_Styles, d->sd_MenuBar != NULL, d->sd_ExStyles);

	OffsetRect(rect, adj_lt && ! (d->super.sw_Flags & SITF_FixedX) ? adj_lt->left - rect->left : 0,
	                 adj_lt && ! (d->super.sw_Flags & SITF_FixedY) ? adj_lt->top  - rect->top  : 0);

	rect->bottom += height;
}

/* Geometry of widgets have changed : reflect changes on child windows */
void SIT_MoveWidgets(SIT_Widget w)
{
	SIT_Widget c, parent;
	HDWP       hdwp;
	HWND       wnd;

	if (w->sw_Type == SIT_DIALOG)
	{
		RECT rect, pos;

		/* Dialog size (not pos) might have changed */
		GetWindowRect(w->sw_Handle, &pos);
		SIT_AdjustWindowRect((SIT_Dialog) w, &rect, &pos);
		if (memcmp(&pos, &rect, sizeof pos))
		{
			/* It will trigger a WM_SIZE event - need to ignore this one */
			w->sw_Flags |= SITF_GeometrySet;
			MoveWindow(w->sw_Handle, rect.left, rect.top, rect.right - rect.left,
				rect.bottom - rect.top, TRUE);
			SIT_UpdateLayeredWindow(w);
			c = ((SIT_Dialog)w)->sd_Status;
			if (c) {
				SIT_SetStatusPart(c);
				SendMessage(c->sw_Handle, WM_SIZE, 0, 0);
			}
		}
	}

	hdwp = BeginDeferWindowPos(w->sw_Children.lh_Count);

	for (c = HEAD(w->sw_Children); c; NEXT(c))
	{
		RECT rect;
		Rect box = c->sw_Box;

		if ((c->sw_Flags & SITF_TopLevel) || !c->sw_Visible) continue;

		box.width  -= box.x;
		box.height -= box.y;
		for (parent = w; ! parent->sw_SuperProc && parent->sw_Type != SIT_DIALOG && parent->sw_Type != SIT_CANVAS;
			 parent = parent->sw_Parent)
			box.x += parent->sw_Box.x, box.y += parent->sw_Box.y;

		GetWindowRect(c->sw_Handle, &rect);
		/* When sending UDM_SETBUDDY the size of the editbox will be changed, we need to (re)add it there */
		if (c->sw_Type == SIT_EDITBOX && (wnd = ((SIT_EditBox)c)->se_UpDown))
		{
			RECT r;
			GetWindowRect(wnd, &r);
			UnionRect(&rect, &r, &rect);
		}
		ScreenToClient(parent->sw_Handle, (LPPOINT) &rect.left);
		ScreenToClient(parent->sw_Handle, (LPPOINT) &rect.right);

		/* Avoid useless refresh */
		Bool newsz = box.width != rect.right - rect.left || box.height != rect.bottom - rect.top;

		if (box.x != rect.left || box.y != rect.top || newsz)
		{
			switch ((int)c->sw_Type) {
			case SIT_COMBOBOX:
				wnd = (HWND) SendMessage(c->sw_Handle, CBEM_GETCOMBOCONTROL, 0, 0);
				SetWindowPos(wnd, NULL, 0, 0, box.width, box.height +
					SIT_ComboBoxItemHeight(c), SWP_NOACTIVATE);
				break;
			case SIT_CANVAS: /* CS_HREDRAW | CS_VREDRAW not set */
				if (c->sw_Flags & SITF_HandleEraseBg) {
					#define canvas    ((SIT_Canvas)c)
					GetClientRect(c->sw_Handle, &rect);
					canvas->sc_Box.cx = rect.right;
					canvas->sc_Box.cy = rect.bottom;
					#undef canvas
					InvalidateRect(c->sw_Handle, NULL, FALSE);
				}
				break;
			case SIT_EDITBOX:
				wnd = ((SIT_EditBox)c)->se_UpDown;
				if (wnd)
				{
					int w = GetSystemMetrics(SM_CXHSCROLL);
					box.width -= w+2;
					hdwp = DeferWindowPos(hdwp, wnd, NULL, box.x+box.width-2, box.y,
						w, box.height, SWP_NOZORDER);
				}
			}

			hdwp = DeferWindowPos(hdwp, c->sw_Handle, NULL, box.x, box.y, box.width, box.height, SWP_NOZORDER);

			WNDPROC proc = (WNDPROC) GetClassLong(c->sw_Handle, GCL_WNDPROC);

			// Children that use the SIT_SubClassHandler will get a WM_SIZE event */
			if (HAS_EVT(c, SITE_OnResize) && newsz)
			{
				ULONG sz[] = {box.width, box.height};
				if (c->sw_Flags & SITF_NCMargins)
					sz[0] -= c->sw_Margins[0] + c->sw_Margins[2],
					sz[1] -= c->sw_Margins[1] + c->sw_Margins[3];
				SIT_ApplyCallback(c, sz, SITE_OnResize);
			}
			/* Will tell to use an optimized background erasure proc */
			if (c->sw_Flags & SITF_HandleEraseBg)
				c->sw_Flags |= SITF_PostProcess;
			/* Do not process the WM_SIZE msg, we will do it here */
			if (proc == SIT_SubClassHandler)
				c->sw_Flags |= SITF_GeometrySet;
		}

		if (c->sw_Children.lh_Count > 0)
			/* Also manage child widgets */
			SIT_MoveWidgets(c);
	}
	EndDeferWindowPos(hdwp);
}

DLLIMP int SIT_ManageWidget(SIT_Widget w)
{
	if (w->sw_Manage == NULL)
	{
		/* Manage first its child */
		if (SIT_LayoutWidgets(w, w->sw_Visible ? KeepDialogSize : FitUsingInitialBox) == FALSE)
			SIT_Log(SIT_CRITICAL, "Error laying out widgets from '%S' (usually check for circular ref among attachments)", w->sw_Name);

		if (w->sw_Type == SIT_DIALOG && w->sw_Visible == 0)
		{
			/* Initially center dialog on nearest monitor where mouse currently is */
			POINT pt;
			RECT size, pos;
			MONITORINFO info = {.cbSize = sizeof info};
			GetCursorPos(&pt);
			HMONITOR hmon = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
			GetMonitorInfo(hmon, &info);
			pos.left = (info.rcWork.right + info.rcWork.left - w->sw_Box.width)  / 2;
			pos.top  = (info.rcWork.bottom - info.rcWork.top - w->sw_Box.height) * 2 / 5 + info.rcWork.top;

			SIT_AdjustWindowRect((SIT_Dialog) w, &size, &pos);
			// fprintf(stderr, "setting '%S' to %ldx%ld\n", w->sw_Name, size.right - size.left, size.bottom - size.top);
			SetWindowPos(w->sw_Handle, NULL, size.left, size.top, size.right - size.left,
				size.bottom - size.top, SWP_NOZORDER);
			SIT_UpdateLayeredWindow(w);
			ShowWindow(w->sw_Handle, SW_SHOW);
			GetClientRect(w->sw_Handle, &size);
		}
		SIT_MoveWidgets(w);

		if (w->sw_Type == SIT_DIALOG && ((SIT_Dialog)w)->sd_Type == SITV_ModalEvt)
		{
			/* Do not exit until this dialog is closed */
			int old = exiting;
			SIT_Main();
			if (exiting == 2)
				exiting = old;
		}
		return 1;
	}
	else return w->sw_Manage(w, NULL, NULL); // Custom manage
}

/* Free memory and resources allocated for widget */
void SIT_DestroyWidget(SIT_Widget w)
{
	SIT_Callback next, cbl;
	SIT_Timer t, tnext;

	if (w->sw_Flags & SITF_IsLocked)
	{
		w->sw_Flags |= SITF_BeingDestroyed;
		/*
		 * If parent is being also destroyed, we need to unlink from it now, because when we
		 * got back here later, memory referenced by sw_Parent would have been already freed.
		 */
		if (w->sw_Parent && (w->sw_Parent->sw_Flags & SITF_BeingDestroyed))
			w->sw_Parent = NULL;
		return;
	}

	if (HAS_EVT(w, SITE_OnFinalize))
		SIT_ApplyCallback(w, NULL, SITE_OnFinalize);
	if (w->sw_SuperProc) SetWindowLong(w->sw_Handle, GWL_WNDPROC, (LONG) w->sw_SuperProc);
	if (w->sw_Handle) RemoveProp(w->sw_Handle, sit_class);
//	if (w->sw_BgBrush) DeleteObject(w->sw_BgBrush);
	if (w->sw_GC) GFX_Free(w->sw_GC);
	if (w->sw_Font && w->sw_Font != sithfont) DeleteObject(w->sw_Font);
	if (w->sw_Flags & SITF_CustomCursor) DestroyCursor(w->sw_Cursor);
	if (w->sw_Finalize == NULL)
	{
		if (w->sw_Class != WidgetClass)
		{
			TagList * tags;
			/* If no finalize cb, free all SIT_STR fields */
			for (tags = w->sw_Class; tags->tl_TagID != SIT_TagEnd; tags ++)
			{
				if (tags->tl_Type != SIT_STR) continue;
				STRPTR * str = (APTR) w + tags->tl_Arg;
				if (*str) free(*str);
			}
		}
	}
	else w->sw_Finalize(w, NULL, NULL);

	if (w->sw_Title)     free(w->sw_Title);
	if (w->sw_BuddyText) free(w->sw_BuddyText);
	if (w->sw_ToolTip)   free(w->sw_ToolTip);

	/* Unregister events */
	for (cbl = HEAD(w->sw_Callbacks); cbl; next = (APTR) cbl->sc_Node.ln_Next, free(cbl), cbl = next)
	{
		if (cbl->sc_Event == SITE_OnClipChanged)
			ChangeClipboardChain(w->sw_Handle, w->sw_Chain);
	}

	for (t = tnext = HEAD(app->sa_Timers); t; t = tnext)
	{
		NEXT(tnext);
		if (t->w == w) {
			KillTimer(t->w->sw_Handle, t->timerid);
			ListRemove(&app->sa_Timers, &t->node);
			free(t);
		}
	}
	if (w->sw_Parent)
		ListRemove(&w->sw_Parent->sw_Children, &w->sw_Node);

	free(w);
}

/* Remove properties added to window for keeping widget class */
void SIT_DestroyChildren(SIT_Widget w)
{
	SIT_Widget child;

	while ((child = (APTR) ListRemHead(&w->sw_Children)))
	{
		/* Subclassed children will have their own WM_DESTROY */
		if (child->sw_SuperProc)
		{
			child->sw_Parent = NULL;
			continue;
		}
		/* Locked children will detach from 'child' that way */
		child->sw_Flags |= SITF_BeingDestroyed;
		if (child->sw_Children.lh_Count > 0)
			SIT_DestroyChildren(child);

		SIT_DestroyWidget(child);
	}
}

void SIT_InitiateReflow(SIT_Widget w)
{
	Bool resize = w->sw_ResizePolicy == SITV_Auto;
	SIT_Widget c = w;

	while (w->sw_Type != SIT_DIALOG)
	{
		if (resize) {
			w->sw_OptimalBox.cx = w->sw_OptimalBox.cy = -1;
			memset(&w->sw_Box, 0, sizeof w->sw_Box);
			/* Previously hidden control: restore their original size */
			if (w->sw_Flags & SITF_RestoreSize) {
				w->sw_Box.width = w->sw_CurrentBox.cx;
				w->sw_Box.height = w->sw_CurrentBox.cy;
				w->sw_Flags &= ~SITF_RestoreSize;
			}
		}
		w = w->sw_Parent;
	}

	if (w->sw_Visible && (w->sw_Flags & SITF_GeomNotified) == 0)
	{
		w->sw_Flags |= SITF_GeomNotified;
		if ((w->sw_Flags & (SITF_FixedWidth | SITF_FixedHeight)) == 0)
			((SIT_Dialog)w)->sd_MinSize.cx = 0; /* Recompute max/min size */
//		fprintf(stderr, "Initiate reflow from '%S'...%d\n", c->sw_Name, c->sw_Type == SIT_DIALOG ?
//			KeepDialogSize : geomPolicy[w->sw_ResizePolicy]);
		PostMessageW(w->sw_Handle, WM_GEOMETRY, 0, c->sw_Type == SIT_DIALOG && (c->sw_Flags & SITF_KeepDiagSize) ?
			KeepDialogSize : geomPolicy[w->sw_ResizePolicy]);
		c->sw_Flags &= ~SITF_KeepDiagSize;
	}
}

/*
 * Very Important Function : allow to set widget's property at runtime.
 */
DLLIMP void SIT_SetValues(SIT_Widget w, ...)
{
	va_list args;

	if (w == NULL) return;

	va_start(args, w);
	SIT_ParseTags(w, args, w->sw_Class);
	va_end(args);

	/*
	 * PostProcess cannot be called from SIT_ParseTags because at creation time
	 * the control has not been created yet (sw_Handle == NULL).
	 */
	if (w->sw_PostProcess)
	{
		w->sw_PostProcess(w, NULL, NULL);
		w->sw_PostProcess = NULL; // Avoid reusing the callback
	}
	else if (w->sw_Flags & SITF_PostProcess)
	{
		SIT_OnVal cd = {.stage = SITV_PostProcess};
		w->sw_Flags &= ~SITF_PostProcess;
		SIT_ApplyCallback(w, &cd, SITE_OnSetOrGet);
	}

	/* Check for geometry changes */
	if (w->sw_Flags & SITF_GeometryChanged)
	{
		if (w->sw_ResizePolicy != SITV_Fixed) SIT_InitiateReflow(w);
		w->sw_Flags &= ~ SITF_GeometryChanged;
	}
}

static void SIT_GetTitle(SIT_Widget w)
{
	int    utf8len;
	int    len = GetWindowTextLength(w->sw_Handle) + 1;
	int    max = w->sw_Title ? strlen(w->sw_Title) + 1 : 0;
	LPWSTR mem = alloca(len * sizeof *mem);

	GetWindowText(w->sw_Handle, mem, len);

	utf8len = WideCharToMultiByte(CP_UTF8, 0, mem, -1, NULL, 0, NULL, NULL) + 1;

	if (utf8len > max)
		w->sw_Title = realloc(w->sw_Title, max = utf8len);

	len = WideCharToMultiByte(CP_UTF8, 0, mem, -1, w->sw_Title, max, NULL, NULL);
	w->sw_Title[len] = 0;
}

static APTR SIT_GetPos(SIT_Widget w, int offset)
{
	if (w->sw_Type == SIT_DIALOG)
	{
		#define	d   ((SIT_Dialog)w)
		RECT r, gap = {};
		GetWindowRect(w->sw_Handle, &r);
		AdjustWindowRectEx(&gap, d->sd_Styles, d->sd_MenuBar != NULL, d->sd_ExStyles);
		w->sw_Fixed.x = r.left - gap.left;
		w->sw_Fixed.y = r.top  - gap.top;
		#undef d
	}
	else return &w->sw_Box.x + offset;
	return &w->sw_Fixed.x + offset;
}

static void SIT_GetSize(SIT_Widget w, int offset, int * buffer)
{
	if (w->sw_Type != SIT_DIALOG)
	{
		int wh[2];
		wh[0] = w->sw_Box.width  - w->sw_Box.x;
		wh[1] = w->sw_Box.height - w->sw_Box.y;
		if (w->sw_Flags & SITF_NCMargins)
			wh[0] -= w->sw_Margins[0] + w->sw_Margins[2],
			wh[1] -= w->sw_Margins[1] + w->sw_Margins[3];
		*buffer = wh[offset];
	}
	else *buffer = (&w->sw_Box.width)[offset];
}

#define	lb ((SIT_ListBox)w)
static void SIT_GetListArg(SIT_Widget w, int mask, APTR buffer)
{
	LVITEM item = {.mask = mask, .stateMask = LVIS_SELECTED, .iItem = lb->sl_CurRow};
	ListView_GetItem(w->sw_Handle, &item);
	if (mask == LVIF_STATE) * (Bool *) buffer = item.state & LVIS_SELECTED ? True : False;
	else * (LPARAM *) buffer = item.lParam;
}

static void SIT_GetListText(SIT_Widget w, STRPTR * ret)
{
	WCHAR  text[256];
	LVITEM item = {.mask = LVIF_TEXT, .pszText = text, .cchTextMax = DIM(text), .iItem =
				   lb->sl_CurRow&0xffffff, .iSubItem = lb->sl_CurRow>>24};
	ListView_GetItem(w->sw_Handle, &item);
	int len = WideCharToMultiByte(CP_UTF8, 0, item.pszText, -1, NULL, 0, NULL, NULL);
	*ret = realloc(lb->sl_CurItem, len);
	if (*ret) WideCharToMultiByte(CP_UTF8, 0, item.pszText, -1, *ret, len, NULL, NULL);
}
#undef lb

DLLIMP void SIT_GetValues(SIT_Widget w, ...)
{
	va_list vargs;
	int     tag, usage = 0;

	if (w == NULL) return;
	va_start(vargs, w);
	while ((tag = va_arg(vargs, int)))
	{
		/* Look in classArgs for definition of that tag */
		TagList * stack[5];
		TagList * args;

		if (tag >= SIT_TagUser)
		{
			SIT_OnVal cd = {.tag = tag, .stage = SITV_Get};
			cd.ptr = va_arg(vargs, APTR);
			SIT_ApplyCallback(w, &cd, SITE_OnSetOrGet);
			continue;
		}

		for (args = w->sw_Class; args->tl_TagID != SIT_TagEnd; )
		{
			/* Super class */
			if (args->tl_TagID == SIT_SuperClass)
			{
				stack[usage ++] = args + 1;
				args = (TagList *) args->tl_Arg;
				continue;
			}
			if (args->tl_TagID != tag)
			{
				args ++;
				if (args->tl_TagID == 0 && usage > 0)
					args = stack[-- usage];
				continue;
			}

			tag = (args->tl_Type == SIT_ABBR ? args->tl_Arg : 0);

			do {
				args -= tag & 0xff; tag >>= 8;
				APTR field = (STRPTR) w + args->tl_Arg;
				switch (args->tl_TagID) {
				case SIT_Title:       SIT_GetTitle(w); break;
				case SIT_HotKey:      SIT_EditGetText((APTR)w); break;
				case SIT_X:
				case SIT_Y:           field = SIT_GetPos(w, args->tl_TagID - SIT_X); break;
				case SIT_Visible:
				{	SIT_Widget d;
					for (d = w; d->sw_Visible && d->sw_Type != SIT_DIALOG; d = d->sw_Parent);
					va_arg(vargs, Bool *)[0] = d->sw_Visible;
				}	continue;
				/* Yeah, virtual functions would be better */
				case SIT_Width:
				case SIT_Height:      SIT_GetSize(w, args->tl_TagID - SIT_Width, va_arg(vargs, int *)); continue;
				case SIT_TargetRow:   ((SIT_ListBox)w)->sl_CurRow = va_arg(vargs, int); continue;
				case SIT_RowTagArg:   SIT_GetListArg(w, LVIF_PARAM, va_arg(vargs, APTR *)); continue;
				case SIT_RowSelArg:   SIT_GetListArg(w, LVIF_STATE, va_arg(vargs, Bool *)); continue;
				case SIT_RowTextArg:  SIT_GetListText(w, va_arg(vargs, STRPTR *)); continue;
				case SIT_ItemPerPage: va_arg(vargs, int *)[0] = ListView_GetCountPerPage(w->sw_Handle); continue;
				case SIT_ClipFormats: SIT_GetClipboardFormats(); break; /* Will refresh *field */
				case SIT_CurrentDir:
				{	int    len = GetCurrentDirectory(0, NULL);
					LPWSTR str = alloca(sizeof *str * len);
					GetCurrentDirectory(len, str);
					len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
					STRPTR utf8 = realloc(((SIT_App)w)->sa_CurrentDir, len);
					if (utf8) WideCharToMultiByte(CP_UTF8, 0, str, -1, ((SIT_App)w)->sa_CurrentDir = utf8, len, NULL, NULL);
				}	break;
				case SIT_ItemCount:
					if (w->sw_Type == SIT_LISTBOX) {
						va_arg(vargs, int *)[0] = ListView_GetItemCount(w->sw_Handle); continue;
					}
				}
				switch (args->tl_Type) {
				case SIT_CTRL:
				case SIT_PTR:  va_arg(vargs, APTR *)[0]   = * (APTR *)   field; break;
				case SIT_INT:  va_arg(vargs, int *)[0]    = * (int *)    field; break;
				case SIT_LONG: va_arg(vargs, long *)[0]   = * (long *)   field; break;
				case SIT_DBL:  va_arg(vargs, double *)[0] = * (double *) field; break;
				case SIT_BOOL: va_arg(vargs, Bool *)[0]   = * (Bool *)   field; break;
				case SIT_STR:  va_arg(vargs, STRPTR *)[0] = * (STRPTR *) field;
				}
			}
			while (tag);
		}
	}
	va_end(vargs);
}


/* Setup a decent font for widget */
void SIT_SetDefaultFont(SIT_Widget w)
{
	SIT_Widget p;

	if (w->sw_Font == 0)
	{
		/* Inherit from parent dialog if not explicitely set */
		for (p = w; p && p->sw_Type != SIT_DIALOG; p = p->sw_Parent);

		if (p) w->sw_Font = p->sw_Font;
	}

	if (w->sw_Font)
		SendMessage(w->sw_Handle, WM_SETFONT, (WPARAM) w->sw_Font, FALSE);
}

/* Was originally a macro :-( */
Bool IsVisible(SIT_Widget w)
{
	/*
	 * Some controls despite being seen as children within SIT, are not child of their
	 * (SIT) parent window (like children of SIT_FRAME). Therefore, we have to check
	 * the whole hierarchy if any of the parents have been hidden, we cannot use
	 * IsWindowVisible(HWND).
	 */
	while (w->sw_Visible && w->sw_Type != SIT_DIALOG)
		w = w->sw_Parent;

	return w->sw_Type == SIT_DIALOG;
}

DLLIMP void SIT_RemoveWidget(SIT_Widget w)
{
	#define	dlg     ((SIT_Dialog)d)
	SIT_Widget d;
	Bool msg = False;

	if (w == NULL || (w->sw_Flags & SITF_BeingDestroyed)) return;
	for (d = w; d && d->sw_Type != SIT_DIALOG; d = d->sw_Parent);
	if (w->sw_Flags & SITF_IsLocked) {
		PostMessageW(d->sw_Handle, WM_DELAYDESTROY, 0, (LPARAM) w->sw_Handle);
		return;
	}

	if (w->sw_Type == SIT_DIALOG && dlg->sd_Modal)
		EnableWindow(dlg->sd_Modal, TRUE), dlg->sd_Modal = NULL;
	if (dlg->sd_Status == w) dlg->sd_Status = NULL;

	/* Remove from MAX constraint chain */
	SIT_Widget prev = (APTR) w->sw_Max.ln_Prev;
	SIT_Widget next = (APTR) w->sw_Max.ln_Next;

	if (prev) prev->sw_Max.ln_Next = (APTR) next, msg = True;
	if (next) next->sw_Max.ln_Prev = (APTR) prev, msg = True;

	if ((SIT_ReassignAttachments(w) || msg) && (d->sw_Flags & SITF_GeomNotified) == 0)
	{
		d->sw_Flags |= SITF_GeomNotified;
		PostMessageW(d->sw_Handle, WM_GEOMETRY, 0, geomPolicy[d->sw_ResizePolicy]);
	}
	HANDLE hwnd = w->sw_Handle;
	w->sw_Flags |= SITF_BeingDestroyed;
	SIT_DestroyChildren(w);
	SIT_DestroyWidget(w);
	DestroyWindow(hwnd);
	#undef dlg
	if (app->super.sw_Children.lh_Count == 0)
		PostQuitMessage(0);
}
